package com.yhos.platform.oopston.core.aop;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yhos.platform.oopston.core.manager.TraceConstant;
import com.yhos.platform.oopston.core.manager.TraceParameterThreadLocalManager;
import com.yhos.platform.oopston.core.utils.JacksonUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;

/**
 * 出入参解析（将出入参Json化并记录日志、解析设备信息、解析链路信息、异常统一处理等）
 */
public class ParamsAspect {

    private Logger logger = LoggerFactory.getLogger(ParamsAspect.class);
    // 请求参数处理类
    private ParamsManager paramsManager = ParamsManager.getInstance();
    // 参数ThreadLocal处理类
    private ParamsThreadLocalManager paramsThreadLocalManager = ParamsThreadLocalManager.getInstance();
    // 日志Trace参数ThreadLocal处理类
    private TraceParameterThreadLocalManager traceParameterThreadLocalManager = TraceParameterThreadLocalManager.getInstance();
    // JSON处理类
    private ObjectMapper objectMapper = JacksonUtils.getInstance();
    // 返回结果处理类
    private ParamsReturnHandler paramsReturnHandler = ParamsReturnHandler.getInstance();
    // 应用名
    private String applicationName;
    // 应用IP
    private String applicationIP;

    public void controllerMethodPointcut() {

    }

    public Object processAspect(ProceedingJoinPoint point) throws Throwable {
        boolean ifException = false;
        long startTime = System.currentTimeMillis();
        Object[] args = point.getArgs();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> aClass = point.getTarget().getClass();
        String urlPath = aClass.getAnnotation(RequestMapping.class).value()[0];
        urlPath = urlPath + "/" + getUrlPath(method);//获取请求的Controller Mapping URL
        Map<String, Object> requestMap = null;
        String responseContent = "";
        Object dto;
        try {
            // 根据请求的Controller类、方法名、参数、需要过滤的参数类型以及需要过滤的参数注解得到
            Map<String, Object> requestParamsMap = paramsManager.getParamValueMap(aClass, method, args,
                    ParamsManager.EXCLUDE_PARAMS, ParamsManager.EXCLUDE_ANNOTATIONS);
            // 根据请求Header信息
            Map<String, Object> extDefineParams = getExtDefineParams();
            RequestContextHolder.currentRequestAttributes().setAttribute("extDefineParams", extDefineParams, SCOPE_REQUEST);
            paramsThreadLocalManager.putParams(requestParamsMap, extDefineParams);
            traceParameterThreadLocalManager.putParams(extDefineParams);
            ParamsBean params = paramsThreadLocalManager.getParams();
            requestMap = params.getAllRequestParamsMap();

            dto = point.proceed(getProceedArgs(args, requestMap));
            if (dto != null) {
                paramsReturnHandler.doReturnHandler(dto);
            }
            // 处理返回结果
            responseContent = getObjectContent(dto);
        } catch (Exception ex) {
            ifException = true;
            responseContent = ex.getMessage();
            // 非业务异常记录Error Log
            logger.error(urlPath + "\n , request content : " + getObjectContent(requestMap) + "\n , error msg : " + ex.getMessage(), ex);
            throw ex;
        } finally {
            // 记录整个请求的出入参数以及响应时长
            long endTime = System.currentTimeMillis();
            long timestamp = endTime - startTime;
            String requestContent = getObjectContent(requestMap);
            String respJson = "{" +
                    "\"URL\":" + "\"" + urlPath + "\"" + "," +
                    "\"Request\":" + requestContent + "," +
                    "\"Response\":" + responseContent + "," +
                    "\"Timestamp\":" + "\"" + timestamp + "ms\"" +
                    "}";
            logger.info(respJson);
            if (!ifException) {
                // 清空当前线程对应的本地变量，防止ThreadLocal线程重用以及内存溢出
                paramsThreadLocalManager.removeParams();
                traceParameterThreadLocalManager.removeParams();
            }
        }
        return dto;
    }

    private String getUrlPath(Method method) {
        String[] urls = null;
        if (method.getAnnotation(PostMapping.class) != null) {
            PostMapping map = method.getAnnotation(PostMapping.class);
            urls = map.value();
        } else if (method.getAnnotation(GetMapping.class) != null) {
            GetMapping map = method.getAnnotation(GetMapping.class);
            urls = map.value();
        } else if (method.getAnnotation(RequestMapping.class) != null) {
            RequestMapping map = method.getAnnotation(RequestMapping.class);
            urls = map.value();
        }
        if (urls != null && urls.length > 0) {
            return urls[0];
        }
        return "";
    }

    private Map<String, Object> getExtDefineParams() {
        Map<String, Object> extDefineMap = new HashMap<>();
        if (RequestContextHolder.getRequestAttributes() != null) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            if (StringUtils.isNoneBlank(applicationIP)) {
                extDefineMap.put(TraceConstant.REQUEST_IP, applicationIP);
            }
            //平台
            String platform = request.getHeader(TraceConstant.PLATFORM);
            if (StringUtils.isNotBlank(platform)) {
                extDefineMap.put(TraceConstant.PLATFORM, platform);
            }
            //来源
            String source = request.getHeader(TraceConstant.SOURCE);
            if (StringUtils.isNotBlank(source)) {
                extDefineMap.put(TraceConstant.SOURCE, source);
            }
            //机型
            String model = request.getHeader(TraceConstant.MODEL);
            if (StringUtils.isNotBlank(model)) {
                extDefineMap.put(TraceConstant.MODEL, model);
            }
            //系统
            String os = request.getHeader(TraceConstant.OS);
            if (StringUtils.isNotBlank(os)) {
                extDefineMap.put(TraceConstant.OS, os);
            }
            //版本
            String version = request.getHeader(TraceConstant.VERSION);
            if (StringUtils.isNotBlank(version)) {
                extDefineMap.put(TraceConstant.VERSION, version);
            }
            //用户访问口令
            String userToken = request.getHeader(TraceConstant.USER_TOKEN);
            if (StringUtils.isNotBlank(userToken)) {
                extDefineMap.put(TraceConstant.USER_TOKEN, userToken);
            }
            //调用来源，一般是指哪一个项目
            String fromSource = request.getHeader(TraceConstant.FROM_SOURCE);
            if (StringUtils.isBlank(fromSource)) {
                fromSource = applicationName;
            }
            extDefineMap.put(TraceConstant.FROM_SOURCE, fromSource);
            //来源的UUID，为空时自动生成一个，用于记录完整调用链路，后续用Spring Cloud替换
            String fromSourceId = request.getHeader(TraceConstant.FROM_SOURCE_ID);
            if (StringUtils.isBlank(fromSourceId)) {
                fromSourceId = UUID.randomUUID().toString();
            }
            extDefineMap.put(TraceConstant.FROM_SOURCE_ID, fromSourceId);

            logger.info(" ------- ParamsAspect.getExtDefineParams ------- " + fromSourceId + " ------ ");
//            //灰度发布使用的版本控制字段
//            String versionChoose = request.getHeader(OperatorConstant.VERSION_CHOOSE);
//            if(StringUtils.isNotBlank(versionChoose)){
//                extDefineMap.put(OperatorConstant.VERSION_CHOOSE, versionChoose);
//            }
//            //MockServer指定的url
//            Enumeration<String> headerNames = request.getHeaderNames();
//            while(headerNames.hasMoreElements()) {
//                String headName = headerNames.nextElement();
//                if(headName.startsWith(OperatorConstant.MOCK_PRIFIX)) {
//                    extDefineMap.put(headName, request.getHeader(headName));
//                }
//            }
        }
        return extDefineMap;
    }

    private Object[] getProceedArgs(Object[] args, Map<String, Object> requestMap) throws Exception {
        if(args == null || args.length <= 0){
            return args;
        }

        Object[] newArgs = new Object[args.length];
        for(int i=0; i<args.length; i++){
            Object obj = args[i];
            if(obj instanceof ExtDefineParamsInfo){
                newArgs[i] = objectMapper.readValue(objectMapper.writeValueAsString(requestMap), args[i].getClass());
            }else{
                newArgs[i] = obj;
            }
        }

        return newArgs;
    }

    private String getObjectContent(Object object) {
        if (object == null) {
            return "";
        }

        try {
            return objectMapper.writeValueAsString(object);
        } catch (IOException e) {
            logger.error("getObjectContent writeValueAsString error." + e.getMessage(), e);
        }
        return "";
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    public void setApplicationIP(String applicationIP) {
        this.applicationIP = applicationIP;
    }

}
